﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Threading;
using Microsoft.WindowsAzure;
using Microsoft.WindowsAzure.Diagnostics;
using Microsoft.WindowsAzure.ServiceRuntime;
using Microsoft.WindowsAzure.StorageClient;
using System.Configuration;
using System.IO;
using System.Reflection;
using WAPA.Framework;
using System.Xml.Serialization;
using System.Runtime;
using System.Runtime.InteropServices;

namespace WAPA.Workers.PiCalculator
{
    public class WorkerRole : RoleEntryPoint
    {
        public override void Run()
        {
            Trace.WriteLine("Starting to run", "Notifications");
            while (true)
            {
                try
                {
                    //Get the account from config and create a client for queue calls.                    
                    CloudStorageAccount account = CloudStorageAccount.FromConfigurationSetting("CloudStorageAccount");
                    CloudQueueClient queueClient = account.CreateCloudQueueClient();

                    //Get a the queue that belong to the Hosted Service this instance is part of.
                    CloudQueue taskQueue = queueClient.GetQueueReference(RoleEnvironment.GetConfigurationSettingValue("HostedServiceTag"));

                    Trace.WriteLine("Queue" + RoleEnvironment.GetConfigurationSettingValue("HostedServiceTag") + " referenced", "Notifications");

                    if (taskQueue == null)
                    {
                        //There is no queue for this instance to monitor.
                        //Wait and retry till it appears.
                        Thread.Sleep(10);
                        continue;
                    }

                    CloudQueueMessage message = taskQueue.GetMessage();
                    CalculatePiTaskMessage workerTaskMessage = null;

                    Trace.WriteLine("Got message from queue", "Notifications");

                    if (message == null)
                    {
                        Trace.WriteLine("Sleeping 5000ms", "Notifications");

                        Thread.Sleep(5000);
                        continue;
                    }

                    try
                    {
                        //Deserialize the message type.
                        workerTaskMessage = MessageSerializer.DeserializeMessage<CalculatePiTaskMessage>(message.AsString);

                        Trace.WriteLine("Message deserialized", "Notifications");

                        //Since we assume a succesfull execution we simply delete the message instantly instead of after task completion (like normally).
                        taskQueue.DeleteMessage(message);

                        Trace.WriteLine("Message deleted", "Notifications");
                        //We have deserialized and deleted our message. Now let's process it.
                        ExecuteTask(workerTaskMessage);
                        Trace.WriteLine("Task executed", "Notifications");
                    }
                    catch
                    {
                        //This message is not deserializable and will jam the queue so we remove it.
                        try
                        {
                            taskQueue.DeleteMessage(message);
                        }
                        catch (StorageClientException ex)
                        {
                            if (ex.ExtendedErrorInformation.ErrorCode == "MessageNotFound")
                            {
                                //We assume the message was handled (at least deleted) succesfully by another instance.
                            }
                            else
                            {
                                // not the error we were expecting
                                throw;
                            }
                        }
                    }
                }
                catch (Exception ex)
                {
                    //There is not much this role can do to fix the problem so we throw an exeception to start over.
                    ReportFailure("unknown", ex.Message);
                    throw ex;
                }

                Thread.Sleep(5000);
            }
        }

        private void ExecuteTask(CalculatePiTaskMessage workerTaskMessage)
        {
            
            Trace.WriteLine("Execution loop " + workerTaskMessage.RepeatCount, "Notifications");
            for (int repeaterIndex = 0; repeaterIndex < workerTaskMessage.RepeatCount; repeaterIndex++)
            {
                ReportTaskProgress(workerTaskMessage.TaskId, "Executing calculation task with repeatindex " + repeaterIndex + " for " + workerTaskMessage.NoMillionDigits.ToString() + "  digits (" + workerTaskMessage.RepeatCount.ToString() + "-" + workerTaskMessage.RepeatInterval.ToString() + ")");
                //Measure task duration.
                Stopwatch stopWatch = Stopwatch.StartNew();
                //EXECUTE THE TASK Synchroniously.
                Trace.WriteLine("Going to calculate " + workerTaskMessage.NoMillionDigits.ToString() + " for task id " + workerTaskMessage.TaskId, "Notifications");
                string repeatSuffix = workerTaskMessage.RepeatCount > 1 ? "_" + repeaterIndex.ToString() : "";
                string outputFilePath = Calculate(workerTaskMessage.NoMillionDigits, workerTaskMessage.TaskId + repeatSuffix);
                Trace.WriteLine("Calculation ready", "Notifications");
                long taskDuration = stopWatch.ElapsedMilliseconds;
                stopWatch.Stop();

                Trace.WriteLine("Generating output file " + outputFilePath, "Notifications");
                GenerateOutput(workerTaskMessage.NoMillionDigits, outputFilePath + "f");

                //Get need the filesize to calculate throughput to blob storage.
                FileInfo fileInfo = new FileInfo(outputFilePath + "f");
                long outputFileSize = fileInfo.Length;

                LocalResource outputStorage = RoleEnvironment.GetLocalResource("TPIOutputStorage");

                
                //long outputTransferDuration = StoreOutputPayload(workerTaskMessage, outputStorage.RootPath + workerTaskMessage.TaskId + repeatedSuffix + ".dat");
                Trace.WriteLine("Storing output to " + outputFilePath +"f", "Notifications");
                long outputTransferDuration = StoreOutputPayload(workerTaskMessage, outputFilePath + "f");
               

                Trace.WriteLine("Output payload stored from " + outputFilePath, "Notifications");
                //Report task completion
                ReportTaskCompletion(workerTaskMessage, taskDuration, outputTransferDuration, outputFileSize);

                Trace.WriteLine("Completion reported", "Notifications");

                Thread.Sleep(workerTaskMessage.RepeatInterval * 1000);
            }
        }

        private void ReportTaskExecution(CalculatePiTaskMessage workerTaskMessage)
        {
            CloudStorageAccount account = CloudStorageAccount.FromConfigurationSetting("CloudStorageAccount");
            CloudQueueClient queueClient = account.CreateCloudQueueClient();

            //Get a reference to a queue in this storage account.
            CloudQueue notifyQueue = queueClient.GetQueueReference(ManagementContext.NotificationQueueName);

            WorkerNotificationMessage workerNotificationMessage = new WorkerNotificationMessage
            {
                DeploymentId = RoleEnvironment.DeploymentId,
                TaskId = workerTaskMessage.TaskId,
                Message = "Executing task " + workerTaskMessage.TaskId,
                Type = "Normal"
            };

            CloudQueueMessage reportMessage = new CloudQueueMessage(MessageSerializer.Serialize(workerNotificationMessage));

            //Add the messages to the queue.
            notifyQueue.AddMessage(reportMessage);
        }


        private void ReportTaskProgress(string taskId, string message)
        {
            CloudStorageAccount account = CloudStorageAccount.FromConfigurationSetting("CloudStorageAccount");
            CloudQueueClient queueClient = account.CreateCloudQueueClient();

            //Get a reference to a queue in this storage account.
            CloudQueue notifyQueue = queueClient.GetQueueReference(ManagementContext.NotificationQueueName);

            WorkerNotificationMessage workerNotificationMessage = new WorkerNotificationMessage
            {
                DeploymentId = RoleEnvironment.DeploymentId,
                TaskId = taskId,
                Message = message,
                Type = "Notification"
            };

            CloudQueueMessage reportMessage = new CloudQueueMessage(MessageSerializer.Serialize(workerNotificationMessage));

            //Add the messages to the queue.
            notifyQueue.AddMessage(reportMessage);
        }
        private void ReportTaskCompletion(CalculatePiTaskMessage workerTaskMessage, long taskDuration, long outputTransferDuration, long outputFileSize)
        {
            CloudStorageAccount account = CloudStorageAccount.FromConfigurationSetting("CloudStorageAccount");
            CloudQueueClient queueClient = account.CreateCloudQueueClient();

            //Get a reference to a queue in this storage account.
            CloudQueue taskReportQueue = queueClient.GetQueueReference(ManagementContext.ReportQueueName);

            //Construct the report message.
            WorkerTaskReportMessage workerTaskReportMessage = new WorkerTaskReportMessage
            {
                TaskId = workerTaskMessage.TaskId,
                TaskDuration = taskDuration,
                OutputTransferDuration = outputTransferDuration,
                OutputSize = outputFileSize,
                DeploymentId = RoleEnvironment.DeploymentId,
                StorageAccountName = workerTaskMessage.StorageAccountName,
                StorageAccessKey = workerTaskMessage.StorageAccessKey,
                StorageBlobEndPointUrl = workerTaskMessage.StorageBlobEndPointUrl,
                BlobContainerName = workerTaskMessage.OutputContainerName
            };

            CloudQueueMessage reportMessage = new CloudQueueMessage(MessageSerializer.Serialize(workerTaskReportMessage));

            //Add the messages to the queue.
            taskReportQueue.AddMessage(reportMessage);
        }

        private void ReportFailure(string taskId, string errorMessage)
        {
            CloudStorageAccount account = CloudStorageAccount.FromConfigurationSetting("CloudStorageAccount");
            CloudQueueClient queueClient = account.CreateCloudQueueClient();

            //Get a reference to a queue in this storage account.
            CloudQueue notifyQueue = queueClient.GetQueueReference(ManagementContext.NotificationQueueName);

            WorkerNotificationMessage workerNotificationMessage = new WorkerNotificationMessage
            {
                DeploymentId = RoleEnvironment.DeploymentId,
                TaskId = taskId,
                Message = errorMessage,
                Type = "Failure"
            };

            CloudQueueMessage reportMessage = new CloudQueueMessage(MessageSerializer.Serialize(workerNotificationMessage));

            //Add the messages to the queue.
            notifyQueue.AddMessage(reportMessage);
        }

        private long StoreOutputPayload(CalculatePiTaskMessage workerTaskMessage, string outputFilePath)
        {
            try
            {
                Stopwatch stopWatch = Stopwatch.StartNew();

                CloudBlobClient blobClient = new CloudBlobClient(workerTaskMessage.StorageBlobEndPointUrl, new StorageCredentialsAccountAndKey(workerTaskMessage.StorageAccountName, workerTaskMessage.StorageAccessKey));

                //Get a reference to a container, which may or may not exist.
                CloudBlobContainer container = blobClient.GetContainerReference(workerTaskMessage.OutputContainerName);

                //Create a new container, if it does not exist
                container.CreateIfNotExist();

                //Get a reference to a blob, which may or may not exist.
                CloudBlob blob = container.GetBlobReference(Path.GetFileNameWithoutExtension(outputFilePath));
                Trace.WriteLine("Output blob referenced", "Notifications");
                stopWatch.Start();
                //Move the calculation output from local storage to blob storage.
                try
                {
                    blob.UploadFile(outputFilePath);
                }
                catch(Exception ex)
                {
                    Trace.WriteLine("Error uploading blob. Message:" + ex.Message, "Errors");
                    ReportFailure(workerTaskMessage.TaskId, ex.Message);
                }

                stopWatch.Stop();

                return stopWatch.ElapsedMilliseconds;
            }
            catch (Exception ex)
            {
                LocalResource outputStorage = RoleEnvironment.GetLocalResource("TPIOutputStorage");

                string outputFile = outputStorage.RootPath + "errormessage.txt";
                File.WriteAllText(outputFile, ex.Message);
            }

            return 0;
        }

        private void GenerateOutput(int precision, string outputFile)
        {

            // Create random data to write to the file.
            byte[] dataArray = new byte[precision * 1000000];
            new Random().NextBytes(dataArray);

            using (FileStream
                fileStream = new FileStream(outputFile, FileMode.Create))
            {
                // Write the data to the file, byte by byte.
                for (int i = 0; i < dataArray.Length; i++)
                {
                    fileStream.WriteByte(dataArray[i]);
                }

                // Set the stream position to the beginning of the file.
                fileStream.Seek(0, SeekOrigin.Begin);

                // Read and verify the data.
                for (int i = 0; i < fileStream.Length; i++)
                {
                    if (dataArray[i] != fileStream.ReadByte())
                    {
                        return;
                    }
                }
            }
        
        }

        private string Calculate(int noMillionDigits, string taskId)
        {
            Trace.WriteLine("In calculate method", "Notifications");
            string appRoot = Environment.GetEnvironmentVariable("RoleRoot");

            LocalResource outputStorage = RoleEnvironment.GetLocalResource("TPIOutputStorage");

            string pathToExecutable = Path.Combine(Environment.GetEnvironmentVariable("RoleRoot"), "tpi.exe");
            string outputFile = outputStorage.RootPath + taskId + ".out";

            if(File.Exists(outputFile))
            File.Delete(outputFile);


            //This snippet fixes the 60MB memory limit but needs a group policy change to run without popups.
            //Trace.WriteLine("Previous file removed", "Notifications");
            //ProcessStartInfo psi = new ProcessStartInfo();
            //psi.FileName = pathToExecutable;
            //psi.Arguments = @"-o " + outputFile + " " + noMillionDigits.ToString() + "M";

            //Process p = new Process();
            //p.StartInfo = psi;
            //Trace.WriteLine("Starting PI process", "Notifications");
            //p.Start();
            //Trace.WriteLine("Called it with args:" + psi.Arguments, "Notifications");

            //while (!File.Exists(outputFile))
            //{

            //}

            Trace.WriteLine("Passed the wait loop", "Notifications");

            //TODO:We limit the calculation because of an error in calling processes that take more than 60MB memory. This will be fixed shortly.
            if (noMillionDigits > 10) noMillionDigits = 10;

            ProcessStartInfo psi = new ProcessStartInfo();
            psi.FileName = pathToExecutable;
            psi.Arguments = @"-o " + outputFile + " " + noMillionDigits.ToString() + "M";
            psi.CreateNoWindow = false;
            psi.ErrorDialog = false;
            psi.UseShellExecute = false;
            psi.WindowStyle = ProcessWindowStyle.Hidden;
            psi.RedirectStandardOutput = true;
            psi.RedirectStandardError = true;

            string outString = string.Empty;
            string errString = string.Empty;

            Trace.WriteLine("Going to execute TPI", "Notifications");
            // Start the process with the info we specified.
            // Call WaitForExit and then the using statement will close.
            using (Process exeProcess = Process.Start(psi))
            {
                StreamReader output = exeProcess.StandardOutput;
                StreamReader error = exeProcess.StandardError;
                exeProcess.WaitForExit();
                outString = output.ReadToEnd();
                errString = error.ReadToEnd();
            }
            Trace.WriteLine("TPI Executed", "Notifications");

            Trace.WriteLine("Outputfile: " + outputFile,"Notifications");
            return outputFile;
        }

        public override bool OnStart()
        {
            CloudStorageAccount.SetConfigurationSettingPublisher((configName, configSetter) =>
            {
                // for a console app, reading from App.config
                //configSetter(ConfigurationManager.AppSettings[configName]);
                // OR, if running in the Windows Azure environment
                configSetter(RoleEnvironment.GetConfigurationSettingValue(configName));
            });

            Trace.WriteLine("Started", "Notifications");

            //Report the service has started and want to register and continue to pick up tasks from the task queue.
            CloudStorageAccount account = CloudStorageAccount.FromConfigurationSetting("CloudStorageAccount");
            CloudQueueClient queueClient = account.CreateCloudQueueClient();
            CloudQueue queue = queueClient.GetQueueReference(ManagementContext.RegistrationQueueName);

            Trace.WriteLine("Queue instantiated", "Notifications");
            //Let's verify the exact amount of memory.
            //ulong installedMemory;MEMORYSTATUSEX memStatus = new MEMORYSTATUSEX();if( GlobalMemoryStatusEx( memStatus)){    installedMemory = memStatus.ulTotalPhys;}

            WorkerRegistrationMessage workerRegistrationMessage = new Framework.WorkerRegistrationMessage
            {

                DeploymentId = RoleEnvironment.DeploymentId,
                RoleId = RoleEnvironment.CurrentRoleInstance.Id,
                MachineName = Environment.MachineName,
                OsVersion = Environment.OSVersion.VersionString,
                ProcessorCount = Environment.ProcessorCount

            };

            queue.AddMessage(new CloudQueueMessage(MessageSerializer.Serialize(workerRegistrationMessage)));

            Trace.WriteLine("Registration message send", "Notifications");
            return base.OnStart();
        }
    }

    //[return: MarshalAs(UnmanagedType.Bool)]  
    //[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]  
    //static extern bool GlobalMemoryStatusEx([In, Out] MEMORYSTATUSEX lpBuffer);

    //   [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]  
    // private class MEMORYSTATUSEX  {    
    //       public uint dwLength;  

    //       public uint dwMemoryLoad;    
    //       public ulong ullTotalPhys;    
    //       public ulong ullAvailPhys;    
    //       public ulong ullTotalPageFile;  
    //       public ulong ullAvailPageFile; 
    //       public ulong ullTotalVirtual;    
    //       public ulong ullAvailVirtual;    
    //       public ulong ullAvailExtendedVirtual;
    //       public MEMORYSTATUSEX()    
    //       {        
    //           this.dwLength = (uint)Marshal.SizeOf(typeof(NativeMethods.MEMORYSTATUSEX));    
    //       }
    //   }
}
